using CriWare;
using IQIGame.Onigao.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;

namespace IQIGame.Onigao.Game
{
    //id和audio_category表的的一致
    public enum AudioVolumeType
    {
        Bgm = 1,
        Sfx = 2,
        Voice = 3,
    }

    public enum AudioSourceType
    {
        None = 0,
        BgmSource = 1, // BGM背景音乐 (loop)
        SfxSource = 2, // 音效
        LoopSource = 3, // 循环声音（非背景音乐，BGM同一时间只能播一个，循环声音无限制，但是要主动关闭）
        VoiceSource = 4, // voice 人物语音
        Entity = 5, //实体声音
        //Live2DSource = 5,
    }

    public delegate bool TableVisitorAudio(int id, out string cueName, out string sheetName, out AudioSourceType AudioSourceType, out string selector, out string label);

    public class AudioManager : FrameMonoModule
    {
        #region Constant

        const string AcfFileName = "soultide2sound.acf";

        const string AudioVolumeBgm = "AudioVolume_Bgm";
        const string AudioVolumeSfx = "AudioVolume_Sfx";
        const string AudioVolumeVoice = "AudioVolume_Voice";

        //缓存bgm sheet的数量
        const int CacheBgmSheetNum = 2;


        /// <summary>
        /// 音频分类，调整某类声音的音量或者静音用
        /// </summary>
        public readonly static IReadOnlyDictionary<AudioVolumeType, string> AudioCategoryMapping = new Dictionary<AudioVolumeType, string>()
        {
            { AudioVolumeType.Bgm, "Music" },
            { AudioVolumeType.Sfx, "SFX" },
            { AudioVolumeType.Voice, "Voice" },
        };

        #endregion

        #region Variable

        private CriAtom m_criAtom;

        /// <summary>
        /// 所有的CriAudioSource
        /// </summary>
        private Dictionary<AudioSourceType, CriAudioSource> m_sourceDic = new Dictionary<AudioSourceType, CriAudioSource>();

        /// <summary>
        /// 
        /// </summary>
        private CriAtomExStandardVoicePool m_bgmVoicePool;

        private List<string> m_bgmSheetCacheList = new List<string>();


        /// <summary>
        /// 当前播放音频
        /// </summary>
        private Dictionary<AudioPlayback, bool> m_playbackDic = new Dictionary<AudioPlayback, bool>();

        private HashSet<AudioPlayback> m_toBeRemovedPlayback = new HashSet<AudioPlayback>();

        #endregion

        public static TableVisitorAudio TableVisitorAudio { get; set; }

        private static CriAtomListener _atomListenerInstance;

        public static CriAtomListener AtomListenerInstance
        {
            get
            {
                if (_atomListenerInstance == null)
                {
                    _atomListenerInstance = GameObject.FindObjectOfType<CriAtomListener>();
                }
                return _atomListenerInstance;
            }
        }

        protected override void OnEnterGame()
        {
        }

        protected override void OnInit()
        {
            FrameworkEntry.MonoDriver.updateEvent += OnUpdate;

            var global = new GameObject("CRIWARE");
            var sourceRoot = new GameObject("SourceRoot");
            sourceRoot.transform.SetParent(global.transform);
            global.SetActive(false);

            //CriAtom
            var criAtom = global.AddComponent<CriAtom>();
            criAtom.dontDestroyOnLoad = true;
            criAtom.acfFile = FrameworkPath.GetAudioFilePath(AcfFileName, out _);
            m_criAtom = criAtom;

            //CriWareErrorHandler
            var objError = new GameObject(nameof(CriWareErrorHandler));
            objError.transform.SetParent(global.transform);
            var errorHandler = objError.AddComponent<CriWareErrorHandler>();
            errorHandler.dontDestroyOnLoad = false;

            //CriWareInitializer
            var objInitializer = new GameObject(nameof(CriWareInitializer));
            objInitializer.transform.SetParent(global.transform);
            var initializer = objInitializer.AddComponent<CriWareInitializer>();
            initializer.fileSystemConfig.numberOfLoaders = 64;
            initializer.fileSystemConfig.numberOfBinders = 16;
            initializer.atomConfig.maxVirtualVoices = 64;
            initializer.atomConfig.maxBuses = 16;
            initializer.atomConfig.maxCategories = 32;

            // 如果太多流式声音同时播放会报错:lncrease max_streams of CriAtomDbasConfig
            // 默认是最多8个流播,内存最多16
            // initializer.atomConfig.standardVoicePoolConfig.streamingVoices = 8;
            // initializer.atomConfig.standardVoicePoolConfig.memoryVoices = 16;

#if (GameDev && UNITY_STANDALONE) || UNITY_EDITOR
            initializer.atomConfig.usesInGamePreview = true;
#endif

            //CriAtomSource
            foreach (AudioSourceType typeValue in Enum.GetValues(typeof(AudioSourceType)))
            {
                if (typeValue == AudioSourceType.None || typeValue == AudioSourceType.Entity)
                {
                    continue;
                }
                var go = new GameObject(typeValue.ToString());
                go.transform.SetParent(sourceRoot.transform);
                var source = AddAudioSource(go);
                source.Source.volume = 1;
                source.Source.use3dPositioning = false;

                m_sourceDic.Add(typeValue, source);

                switch (typeValue)
                {
                    case AudioSourceType.BgmSource:
                    case AudioSourceType.LoopSource:
                    case AudioSourceType.SfxSource:
                    case AudioSourceType.VoiceSource:
                        break;
                    //case AudioSourceType.Live2DSource:
                    //    var gFCriSourceHandler = go.AddComponent<GFCriSourceHandler>();
                    //    gFCriSourceHandler.Source = source;
                    //    break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            global.SetActive(true);


            #region BgmPool

            int numVoice = 20;
            int maxChannels = 2;
            int maxSamplingRate = 112000;
            bool streamingFlag = true;
            uint identifier = 100;
            //bgm voicePool 
            m_bgmVoicePool = new CriAtomExStandardVoicePool(numVoice, maxChannels, maxSamplingRate, streamingFlag, identifier);
            //bgm设置单独的pool，避免被顶掉
            m_sourceDic[AudioSourceType.BgmSource].Source.player.SetVoicePoolIdentifier(m_bgmVoicePool.identifier);

            #endregion
        }

        protected override void OnShutdown()
        {
            FrameworkEntry.MonoDriver.updateEvent -= OnUpdate;
        }

        protected virtual void OnUpdate(float deltaTime, float unscaleDeltaTime)
        {
            foreach (var kv in m_playbackDic)
            {
                var playback = kv.Key;
                if (playback.isRemoved)
                {
                    //触发audio stop事件
                    playback.InvokeComplete();
                    m_toBeRemovedPlayback.Add(playback);
                }
            }

            foreach (var playback in m_toBeRemovedPlayback)
            {
                m_playbackDic.Remove(playback);
                ClassPool.Put(playback);
            }
            m_toBeRemovedPlayback.Clear();
        }

        /// <summary>
        /// 给一个物体添加AudioSource，一般用作3d声音，要给播放的角色添加
        /// 2d声音不需要，直接用全局的就行了
        /// </summary>
        /// <param name="go"></param>
        /// <returns></returns>
        public CriAudioSource AddAudioSource(GameObject go)
        {
            var caSource = go.AddComponent<CriAtomSource>();
            var source = new CriAudioSource(caSource);
            return source;
        }

        public CriAtomCueSheet GetOrAddCueSheet(string cueSheetName, AudioSourceType sourceType)
        {
            var sheet = CriAtom.GetCueSheet(cueSheetName);

            if (sheet == null)
            {
                var acbPath = FrameworkPath.GetAudioAcbFilePath(cueSheetName, sourceType, out _);
                var awbPath = FrameworkPath.GetAudioAwbFilePath(cueSheetName, sourceType, out var awbInPersistent);
                awbPath = CheckAudioFilePath(acbPath, cueSheetName, awbInPersistent, awbPath);
                sheet = CriAtom.AddCueSheet(cueSheetName, acbPath, awbFile: awbPath);
            }
            return sheet;
        }

        /// <summary>
        /// 播放audio
        /// </summary>
        /// <param name="cueName"></param>
        /// <param name="cueSheetName"></param>
        /// <param name="sourceTypeInt"></param>
        /// <param name="completeEvent"></param>
        /// <param name="ignoreSameBgm"></param>
        public AudioPlayback PlayAudio(string cueName, string cueSheetName, AudioSourceType sourceTypeInt, bool ignoreSameBgm = true)
        {
            var sourceType = (AudioSourceType)sourceTypeInt;
            if (m_sourceDic.TryGetValue(sourceType, out var source))
            {
                _ = GetOrAddCueSheet(cueSheetName, sourceType);

                switch (sourceType)
                {
                    case AudioSourceType.BgmSource:
                        if (source.Source.cueName != cueName || source.Source.cueSheet != cueSheetName)
                        {
                            //播放不同的bgm
                            WhenPlayDiffBgm(source, cueSheetName);
                        }
                        else if (source.Source.status == CriAtomSource.Status.Playing && ignoreSameBgm)
                        {
                            //播放相同的bgm
                            return null;
                        }
                        break;

                    case AudioSourceType.LoopSource:
                    case AudioSourceType.SfxSource:
                    case AudioSourceType.VoiceSource:
                        //case AudioSourceType.Live2DSource:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                return source.PlayAudio(cueName, cueSheetName, sourceType);
            }

            return null;
        }

        public AudioPlayback PlayAudioWithLabel(string cueName, string cueSheetName, AudioSourceType sourceTypeInt, string selector, string label, bool ignoreSameBgm = true)
        {
            var sourceType = (AudioSourceType)sourceTypeInt;
            if (m_sourceDic.TryGetValue(sourceType, out var source))
            {
                _ = GetOrAddCueSheet(cueSheetName, sourceType);

                switch (sourceType)
                {
                    case AudioSourceType.BgmSource:
                        if (source.Source.cueName != cueName || source.Source.cueSheet != cueSheetName)
                        {
                            //播放不同的bgm
                            WhenPlayDiffBgm(source, cueSheetName);
                        }
                        else if ((source.Source.status == CriAtomSource.Status.Playing && ignoreSameBgm)
                                 && (string.IsNullOrEmpty(selector) || string.IsNullOrEmpty(label))
                                )
                        {
                            //播放相同的bgm
                            return null;
                        }
                        break;

                    case AudioSourceType.LoopSource:
                    case AudioSourceType.SfxSource:
                    case AudioSourceType.VoiceSource:
                        //case AudioSourceType.Live2DSource:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                return source.PlayAudioWithLabel(cueName, cueSheetName, sourceType, selector, label);
            }

            return null;
        }

        public AudioPlayback PlayAudioById(int audioId, bool ignoreSameBgm = true)
        {
            if (!TableVisitorAudio(audioId, out var cueName, out var sheetName, out var audioSourceType, out var selector, out var label))
            {
                //Debug.LogError("Cant find AudioCfg, id = " + audioId);
                return null;
            }

            return this.PlayAudioWithLabel(cueName, sheetName, audioSourceType, selector, label, ignoreSameBgm);
        }

        public AudioPlayback PlayAudioByIdAndCheckType(int audioId, AudioSourceType targetType, bool ignoreSameBgm = true)
        {
            if (!TableVisitorAudio(audioId, out var cueName, out var sheetName, out var audioSourceType, out var selector, out var label))
            {
                //Debug.LogError("Cant find AudioCfg, id = " + audioId);
                return null;
            }

            if (targetType != audioSourceType)
            {
                LogGame.LogError($"AudioSourceType not match, audioId:{audioId}, targetType:{targetType}, audioSourceType:{audioSourceType}");
                return null;
            }

            return PlayAudioWithLabel(cueName, sheetName, audioSourceType, selector, label, ignoreSameBgm);
        }


        /// <summary>
        /// 当播放不同的bgm
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cueSheetName"></param>
        void WhenPlayDiffBgm(CriAudioSource source, string cueSheetName)
        {
            //播放不同的bgm
            source.StopSource();

            if (source.Source.cueSheet != cueSheetName)
            {
                if (m_bgmSheetCacheList.Contains(cueSheetName))
                {
                    //有相同的，则放到末尾
                    m_bgmSheetCacheList.Remove(cueSheetName);
                }
                else if (m_bgmSheetCacheList.Count >= CacheBgmSheetNum)
                {
                    //数量超出限制，则移除多的sheet
                    RemoveCueSheet(m_bgmSheetCacheList[0]);
                    m_bgmSheetCacheList.RemoveAt(0);
                }

                m_bgmSheetCacheList.Add(cueSheetName);
            }
        }

        /// <summary>
        /// 校验音频文件地址,主要检测有无文件，以及大小写对不对
        /// </summary>
        /// <param name="acbPath"></param>
        /// <param name="cueSheetName"></param>
        /// <param name="awbInPersistent"></param>
        /// <param name="awbPath"></param>
        /// <returns></returns>
        public string CheckAudioFilePath(string acbPath, string cueSheetName, bool awbInPersistent, string awbPath)
        {
#if UNITY_EDITOR
            var dirPath = Path.GetDirectoryName(acbPath);
            var fileList = Directory.GetFiles(dirPath, "*", SearchOption.TopDirectoryOnly);
            var fileName = cueSheetName + FrameworkPath.AudioAcbExtension;
            if (!fileList.Any(t => fileName == Path.GetFileName(t)))
            {
                //保持大小写一致
                Debug.LogError($"[Dev] cueSheetName({cueSheetName}) is case sensitivity");
                CriAtom.RemoveCueSheet(cueSheetName);
            }

            if (!File.Exists(awbPath))
            {
                awbPath = string.Empty;
            }
            return awbPath;
#else
        //if (!awbInPersistent)
        //{
        //    //在包内,因为安卓不能直接读取
        //    if (!GameData.mediaConfig.awbFileHashSet.ContainsKey(cueSheetName))
        //        awbPath = string.Empty;
        //}

            return awbPath;
#endif
        }

        public void StopSource(AudioSourceType sourceType)
        {
            if (m_sourceDic.TryGetValue(sourceType, out var source))
            {
                source.StopSource();
            }
        }

        /// <summary>
        /// 停止音效
        /// </summary>
        /// <param name="audioPlayback"></param>
        public void StopAudioByBack(AudioPlayback audioPlayback)
        {
            if (audioPlayback == null)
            {
                LogGame.LogWarning("StopAudioByBack : audioPlayback is null");
                return;
            }
            if (m_playbackDic.Remove(audioPlayback))
            {
                audioPlayback.Stop();
                ClassPool.Put(audioPlayback);
            }
        }


        public void StopAllAudio()
        {
            foreach (var item in m_playbackDic)
            {
                item.Key.Stop();
                ClassPool.Put(item.Key);
            }
            m_playbackDic.Clear();
        }

        public void PauseSource(AudioSourceType sourceType, bool pause)
        {
            if (m_sourceDic.TryGetValue(sourceType, out var source))
                source.PauseSource(pause);
        }

        //暂停音效回调
        public void PauseAudioBack(AudioPlayback audioPlayback, bool pause)
        {
            audioPlayback.Pause(pause);
        }

        public void SetVolume(AudioVolumeType volumeType, float volume)
        {
            volume = Mathf.Clamp(volume, 0f, 1f);
            CriAtomExCategory.SetVolume(AudioCategoryMapping[volumeType], volume);
        }

        public float GetVolume(AudioVolumeType volumeType)
        {
            return CriAtomExCategory.GetVolume(AudioCategoryMapping[volumeType]);
        }

        public void SetMute(AudioVolumeType volumeType, bool mute)
        {
            CriAtomExCategory.Mute(AudioCategoryMapping[volumeType], mute);
        }

        public bool IsMute(AudioVolumeType volumeType)
        {
            return CriAtomExCategory.IsMuted(AudioCategoryMapping[volumeType]);
        }

        string GetVolumePrefId(AudioVolumeType volumeType)
        {
            switch (volumeType)
            {
                case AudioVolumeType.Bgm:
                    return AudioVolumeBgm;
                case AudioVolumeType.Sfx:
                    return AudioVolumeSfx;
                case AudioVolumeType.Voice:
                    return AudioVolumeVoice;
                default:
                    throw new ArgumentOutOfRangeException(nameof(volumeType), volumeType, null);
            }
        }

        //不使用的sheet需要移除掉
        public void RemoveCueSheet(string cueSheet)
        {
            CriAtom.RemoveCueSheet(cueSheet);
        }

        //移除该前缀的所有sheet
        public void RemoveCueSheetsWithPrefix(string prefix)
        {
            var toRemoveSheets = ListPool<string>.Get();
            for (var i = 0; i < m_criAtom.cueSheets.Length; i++)
            {
                var sheet = m_criAtom.cueSheets[i];
                if (sheet.name.StartsWith(prefix))
                {
                    toRemoveSheets.Add(sheet.name);
                }
            }

            for (var i = 0; i < toRemoveSheets.Count; i++)
            {
                RemoveCueSheet(toRemoveSheets[i]);
            }
            ListPool<string>.Put(toRemoveSheets);
        }

        private List<int> _bgmList = new List<int>();

        public void AddBgm(int audioId)
        {
            if (!this.CheckBgm(audioId))
            {
                return;
            }
            this._bgmList.Add(audioId);
            this.PlayAudioById(audioId);
        }

        public void SwitchBgm(int audioId)
        {
            if (!this.CheckBgm(audioId))
            {
                return;
            }
            if (this._bgmList.Count > 0)
            {
                this._bgmList.RemoveAt(this._bgmList.Count - 1);
            }

            this._bgmList.Add(audioId);
            this.PlayAudioById(audioId);
        }

        public void RemoveBgm(int audioId)
        {
            if (this._bgmList.Count > 0)
            {
                if (this._bgmList[^1] == audioId)
                {
                    this._bgmList.RemoveAt(this._bgmList.Count - 1);
                    if (this._bgmList.Count > 0)
                    {
                        this.PlayAudioById(this._bgmList[^1]);
                    }
                    else
                    {
                        this.StopSource(AudioSourceType.BgmSource);
                    }
                }
                else
                {
                    // 移除最后添加的那个
                    for (int i = 0; i < this._bgmList.Count; i++)
                    {
                        var nIndex = this._bgmList.Count - 1 - i;
                        if (this._bgmList[nIndex] == audioId)
                        {
                            this._bgmList.RemoveAt(nIndex);
                            break;
                        }
                    }
                }
            }
        }

        private bool CheckBgm(int audioId)
        {
            TableVisitorAudio(audioId, out _, out _, out var audioSourceType, out _, out _);
            return audioSourceType == AudioSourceType.BgmSource;
        }

        //记录当前的bgm
        //public void RecordCurBgm()
        //{
        //    if (m_sourceDic.TryGetValue(AudioSourceType.BgmSource, out var source))
        //    {
        //        m_recordBgmCue = source.Source.cueName;
        //        m_recordBgmSheet = source.Source.cueSheet;
        //    }
        //    else
        //        Debug.LogWarning("Can't get bgm audioSource");
        //}

        //恢复记录的bgm播放
        //public void ResumeLastBgm()
        //{
        //    if (string.IsNullOrEmpty(m_recordBgmCue) || string.IsNullOrEmpty(m_recordBgmSheet))
        //        Debug.LogWarning($"Can't resume last bgm audio, cueName:{m_recordBgmCue}, cueSheet:{m_recordBgmSheet}");
        //    else
        //        PlayAudio(m_recordBgmCue, m_recordBgmSheet, (int)AudioSourceType.BgmSource);

        //    m_recordBgmCue = string.Empty;
        //    m_recordBgmSheet = string.Empty;
        //}

        public void AddPlayback(AudioPlayback pb)
        {
            m_playbackDic.Add(pb, true);
        }

        public void RemovePlayBack(CriAtomSource source)
        {
            //使用该source让 m_playbackActions 不触发事件（因为当播放由Stop函数结束后,也会使AudioPlayBack.status变为Removed，导致触发complete事件）
            foreach (var playbackAction in m_playbackDic)
            {
                if (playbackAction.Key.source == source)
                {
                    m_toBeRemovedPlayback.Add(playbackAction.Key);
                }
            }
            foreach (var playback in m_toBeRemovedPlayback)
            {
                m_playbackDic.Remove(playback);
                ClassPool.Put(playback);
            }
            m_toBeRemovedPlayback.Clear();
        }

        public void SetSourceSelectorLabel(AudioSourceType sourceType, string selector, string label)
        {
            if (m_sourceDic.TryGetValue(sourceType, out var source))
                source.SetSelectorLabel(selector, label);
        }

        public void SetSourceSelectorLabel(int sourceType, string selector, string label)
        {
            SetSourceSelectorLabel((AudioSourceType)sourceType, selector, label);
        }

        public void SetSourceAisacControl(AudioSourceType sourceType, string controlName, float value)
        {
            if (m_sourceDic.TryGetValue(sourceType, out var source))
                source.SetAisacControl(controlName, value);
        }

        public void SetSourceAisacControl(int sourceType, string controlName, float value)
        {
            SetSourceAisacControl((AudioSourceType)sourceType, controlName, value);
        }


        /// <summary>
        /// 设置bgm的淡入淡出时间，毫秒
        /// </summary>
        /// <param name="fadeInTime"></param>
        /// <param name="fadeOutTime"></param>
        //public void SetBgmSourceFadeInAndOutTime(int? fadeInTime, int? fadeOutTime)
        //{
        //    if (m_sourceDic.TryGetValue(AudioSourceType.BgmSource, out var criSource))
        //    {
        //        criSource.Source.player.AttachFader();
        //        m_hasBgmFader = true;
        //        criSource.Source.player.ResetFaderParameters();

        //        if (fadeInTime != null)
        //            criSource.Source.player.SetFadeInTime(fadeInTime.Value);

        //        if (fadeOutTime != null)
        //            criSource.Source.player.SetFadeOutTime(fadeOutTime.Value);
        //    }
        //}

        /// <summary>
        /// 移除bgm的淡入淡出
        /// </summary>
        //public void RemoveBgmSourceFader()
        //{
        //    if (m_hasBgmFader && m_sourceDic.TryGetValue(AudioSourceType.BgmSource, out var criSource))
        //    {
        //        criSource.Source.player.DetachFader();
        //        m_hasBgmFader = false;
        //    }
        //}

        //获取音频的当前播放时间(毫秒)
        public long GetAudioPlayedTime(AudioPlayback audioPlayback)
        {
            if (audioPlayback.isInit)
            {
                return audioPlayback.GetPlayedTime();
            }
            else
            {
                Debug.LogWarning("can't get audioPlayback's play duration");
            }
            return 0;
        }

        //获取 某个音频的总长度(时间 单位毫秒)(不考虑变速什么的)
        public long GetAudioLength(string sheetName, string cueName)
        {
            var acb = CriAtom.GetAcb(sheetName);
            if (acb != null && acb.GetCueInfo(cueName, out var cueInfo))
            {
                return cueInfo.length;
            }
            else
            {
                LogGame.LogWarning("can't get cue's length with sheetName:" + sheetName + " cueName:" + cueName);
            }
            return 1;
        }
    }
}